/**
 * 
 */
package com.williamoneil.charts.client.widget;

import java.util.ArrayList;
import java.util.List;

import com.google.gwt.canvas.dom.client.Context2d;
import com.google.gwt.canvas.dom.client.Context2d.TextBaseline;
import com.williamoneil.charts.client.data.ChartBoundsRect;
import com.williamoneil.charts.client.data.ChartElementsData;
import com.williamoneil.charts.client.data.ChartType;
import com.williamoneil.charts.client.data.DateAxis;
import com.williamoneil.charts.client.data.LineData;
import com.williamoneil.charts.client.data.MovingAverage;
import com.williamoneil.charts.client.data.PriceAxis;
import com.williamoneil.charts.client.data.Tick;
import com.williamoneil.charts.client.util.ChartUtil;
import com.williamoneil.charts.client.util.ColorEntityType;
import com.williamoneil.charts.client.util.Colors;
import com.williamoneil.charts.shared.TradeDateType;

/**
 * @author gudipati
 *
 */
public class PriceVolumeLayer extends ChartOverlay {

	private ChartType chartType = null;
	private int tickSpacing = ChartUtil.DEFAULT_TICK_SPACING;
	
	public PriceVolumeLayer(final ChartBoundsRect chartBounds, final ChartType cType, final int tickSpacing) {
		super(chartBounds);
		
		this.tickSpacing = tickSpacing;
		
		if(cType == null) {
			this.chartType = ChartType.CANDLESTICK;
		} else {
			this.chartType = cType;
		}
	}
	
	/**
	 * @return the chartType
	 */
	public ChartType getChartType() {
		return chartType;
	}

	/**
	 * @param chartType the chartType to set
	 */
	public void setChartType(ChartType chartType) {
		this.chartType = chartType;
	}
	
	/**
	 * @return the tickSpacing
	 */
	public int getTickSpacing() {
		return tickSpacing;
	}

	/**
	 * @param tickSpacing the tickSpacing to set
	 */
	public void setTickSpacing(int tickSpacing) {
		this.tickSpacing = tickSpacing;
	}

	/* (non-Javadoc)
	 * @see com.williamoneil.charts.client.widget.ChartOverlay#drawOverlay()
	 */
	@Override
	public void drawOverlay() {		
		if(super.getChartElements() == null || 
				super.getChartElements().getTicks() == null || super.getChartElements().getTicks().size() == 0) {
			return;
		}
		
		drawPriceChart();
		drawPriceMovingAverageLines();
		drawPriceHiLowLabels();
	}

	private void drawPriceChart() {
		final ChartElementsData elements = super.getChartElements();
		final PriceAxis priceAxis = elements.getPriceAxis();
		final DateAxis dateAxis = elements.getDateAxis();
		final List<Tick> ticks = elements.getTicks();
		final ChartBoundsRect bounds = super.getChartBoundsRect();

		final List<Tick> equalTicks = new ArrayList<Tick>();
		final List<Tick> upTicks = new ArrayList<Tick>();
		final List<Tick> downTicks = new ArrayList<Tick>();
		
		// loop through and plot the down-ticks first
		for(final Tick aData : ticks) {
			if(aData.getTradeDateType() != TradeDateType.TRADE_DATE) {
				continue;
			}
			final int x = dateAxis.getX(aData.getDate());
			
			// we just need to check one point to make sure it is within bounds
			if(!bounds.isXWithinBounds(x)) {
				break;
			}

			if(aData.getPrice().getClose().compareTo(aData.getPrice().getOpen()) < 0) {
				downTicks.add(aData);
			} else if(aData.getPrice().getClose().compareTo(aData.getPrice().getOpen()) > 0){
				upTicks.add(aData);
			} else {
				equalTicks.add(aData);
			}
		}
		
		final Context2d context = super.getGraphicsContext();

		context.beginPath();
		final String upTickColor = Colors.getColorTheme().getColor(ColorEntityType.TICK_UP);
		context.setStrokeStyle(upTickColor);
		context.setFillStyle(upTickColor);
		for(final Tick aData : upTicks) {
			drawUpTick(context, aData, bounds, priceAxis, dateAxis);
		}
		context.stroke();
		
		context.beginPath();
		final String downTickColor = Colors.getColorTheme().getColor(ColorEntityType.TICK_DOWN);
		context.setStrokeStyle(downTickColor);
		context.setFillStyle(downTickColor);
		for(final Tick aData : downTicks) {
			drawDownTick(context, aData, bounds, priceAxis, dateAxis);			
		}
		context.stroke();
		
		context.beginPath();
		final String equalTickColor = Colors.getColorTheme().getColor(ColorEntityType.TICK_NEUTRAL);
		context.setStrokeStyle(equalTickColor);
		context.setFillStyle(equalTickColor);
		for(final Tick aData : equalTicks) {
			drawEqualTick(context, aData, bounds, priceAxis, dateAxis);
		}
		context.stroke();
	}

	private void drawPriceMovingAverageLines() {
		final ChartElementsData elements = super.getChartElements();
		final PriceAxis priceAxis = elements.getPriceAxis();
		final DateAxis dateAxis = elements.getDateAxis();
		final ChartBoundsRect bounds = super.getChartBoundsRect();

		final Context2d context = super.getGraphicsContext();

		final List<LineData> ma1List = new ArrayList<LineData>();
		final List<LineData> ma2List = new ArrayList<LineData>();
		for(final Tick aData : elements.getTicks()) {
			final MovingAverage mAvg1 = aData.getPriceMovingAverage1();
			final MovingAverage mAvg2 = aData.getPriceMovingAverage2();
			
			if(mAvg1 != null || mAvg2 != null) {
				final int x = dateAxis.getX(aData.getDate());
				if(!bounds.isXWithinBounds(x)) {
					break;
				}
				
				if(mAvg1 != null) {
					final int y = priceAxis.getY(mAvg1.getValue(), bounds);
					
					if(bounds.isYWithinBounds(y)) {
						final LineData ma = new LineData(x, y);
						ma1List.add(ma);
					}
				}
			
				if(mAvg2 != null) {
					final int y = priceAxis.getY(mAvg2.getValue(), bounds);
					
					if(bounds.isYWithinBounds(y)) {
						final LineData ma = new LineData(x, y);
						ma2List.add(ma);
					}
				}
			}
		}
		
		if(ma1List.size() > 0) {
			context.beginPath();
			String maColor = Colors.getColorTheme().getColor(ColorEntityType.PRICE_MOVING_AVG_1);
			context.setStrokeStyle(maColor);
			context.setFillStyle(maColor);

			context.moveTo(ma1List.get(0).getX(), ma1List.get(0).getY());
			for(int i=0;i<ma1List.size();i++) {
				final LineData aData = ma1List.get(i);
				context.lineTo(aData.getX(), aData.getY());
			}
			context.stroke();
		}

		if(ma2List.size() > 0) {
			context.beginPath();
			String maColor = Colors.getColorTheme().getColor(ColorEntityType.PRICE_MOVING_AVG_2);
			context.setStrokeStyle(maColor);
			context.setFillStyle(maColor);

			context.moveTo(ma2List.get(0).getX(), ma2List.get(0).getY());
			for(int i=0;i<ma2List.size();i++) {
				final LineData aData = ma2List.get(i);
				context.lineTo(aData.getX(), aData.getY());
			}
			context.stroke();
		}
	}
	
	private void drawPriceHiLowLabels() {
		final ChartElementsData elements = super.getChartElements();
		final PriceAxis priceAxis = elements.getPriceAxis();
		final DateAxis dateAxis = elements.getDateAxis();
		final List<Tick> ticks = elements.getTicks();
		final ChartBoundsRect bounds = super.getChartBoundsRect();
		
		final Context2d context = super.getGraphicsContext();
		context.beginPath();
		context.setFont("8pt Calibri");
		context.setTextBaseline(TextBaseline.BOTTOM);
		
		final String color = Colors.getColorTheme().getColor(ColorEntityType.TICK_HILOW_LABEL);
		context.setStrokeStyle(color);
		context.setFillStyle(color);
		for(final Tick aData : ticks) {
			int y = 0;
			double price = 0;
			if(aData.isHasPriceHighLabel()) {
				price = aData.getPrice().getHigh().doubleValue();
				y = priceAxis.getY(price, bounds) - 7; 
			} else if (aData.isHasPriceLowLabel()) {
				price = aData.getPrice().getLow().doubleValue();
				y = priceAxis.getY(price, bounds) + 15;
			} else {
				continue;
			}
			
			if(!bounds.isYWithinBounds(y)) {
				continue;
			}
			
			final int x = dateAxis.getX(aData.getDate()) - this.tickSpacing;
			if(!bounds.isXWithinBounds(x)) {
				continue;
			}
			
			final String text = "" + (((double) Math.round(price * 100)) / 100);
			context.fillText(text, x, y);
		}
		context.stroke();
		
	}
	
	// draw up-tick on a given context
	public void drawUpTick(final Context2d context, final Tick aData, final ChartBoundsRect bounds, final PriceAxis priceAxis, final DateAxis dateAxis) {
		final int highY = priceAxis.getY(aData.getPrice().getHigh().doubleValue(), bounds);
		final int lowY = priceAxis.getY(aData.getPrice().getLow().doubleValue(), bounds);
		final int openY = priceAxis.getY(aData.getPrice().getOpen().doubleValue(), bounds);
		final int closeY = priceAxis.getY(aData.getPrice().getClose().doubleValue(), bounds);
		
		final int x = dateAxis.getX(aData.getDate());
		
		if(chartType == ChartType.CANDLESTICK) {
			ChartUtil.lineTo(context, x-(tickSpacing/2)-1, lowY, x-(tickSpacing/2)-1, highY);
			context.fillRect(x-tickSpacing, closeY, tickSpacing-1, (openY - closeY));
		} else if (chartType == ChartType.HLOC) {
			ChartUtil.lineTo(context, x-(tickSpacing/2)-1, lowY, x-(tickSpacing/2)-1, highY);
			ChartUtil.lineTo(context, x-tickSpacing, openY, x-(tickSpacing/2)-1, openY);
			ChartUtil.lineTo(context, x-(tickSpacing/2)-1, closeY, x+(tickSpacing/2), closeY);
		} else {
			// defaults to HLC charts
			ChartUtil.lineTo(context, x-(tickSpacing/2)-1, lowY, x-(tickSpacing/2)-1, highY);
			ChartUtil.lineTo(context, x-tickSpacing, closeY, x+(tickSpacing/2), closeY);			
		}
	}
	
	// draw up-tick on a given context
	public void drawDownTick(final Context2d context, final Tick aData, final ChartBoundsRect bounds, final PriceAxis priceAxis, final DateAxis dateAxis) {
		final int highY = priceAxis.getY(aData.getPrice().getHigh().doubleValue(), bounds);
		final int lowY = priceAxis.getY(aData.getPrice().getLow().doubleValue(), bounds);
		final int openY = priceAxis.getY(aData.getPrice().getOpen().doubleValue(), bounds);
		final int closeY = priceAxis.getY(aData.getPrice().getClose().doubleValue(), bounds);

		final int x = dateAxis.getX(aData.getDate());
		
		if(chartType == ChartType.CANDLESTICK) {
			ChartUtil.lineTo(context, x-(tickSpacing/2)-1, lowY, x-(tickSpacing/2)-1, highY);
			context.fillRect(x-tickSpacing, openY, tickSpacing-1, (closeY - openY));
		} else if (chartType == ChartType.HLOC) {
			ChartUtil.lineTo(context, x-(tickSpacing/2)-1, lowY, x-(tickSpacing/2)-1, highY);
			ChartUtil.lineTo(context, x-tickSpacing, openY, x-(tickSpacing/2)-1, openY);
			ChartUtil.lineTo(context, x-(tickSpacing/2)-1, closeY, x+(tickSpacing/2), closeY);
		} else {
			// defaults to HLC charts
			ChartUtil.lineTo(context, x-(tickSpacing/2)-1, lowY, x-(tickSpacing/2)-1, highY);
			ChartUtil.lineTo(context, x-tickSpacing, closeY, x+(tickSpacing/2), closeY);			
		}
	}
	
	// draw a equal-tick on a given context
	public void drawEqualTick(final Context2d context, final Tick aData, final ChartBoundsRect bounds, final PriceAxis priceAxis, final DateAxis dateAxis) {
		final int highY = priceAxis.getY(aData.getPrice().getHigh().doubleValue(), bounds);
		final int lowY = priceAxis.getY(aData.getPrice().getLow().doubleValue(), bounds);
		final int openY = priceAxis.getY(aData.getPrice().getOpen().doubleValue(), bounds);

		final int x = dateAxis.getX(aData.getDate());
		
		if(chartType == ChartType.CANDLESTICK) {
			ChartUtil.lineTo(context, x-(tickSpacing/2)-1, lowY, x-(tickSpacing/2)-1, highY);
			ChartUtil.lineTo(context, x-tickSpacing, openY, x-1, openY);
		} else if (chartType == ChartType.HLOC) {
			ChartUtil.lineTo(context, x-(tickSpacing/2)-1, lowY, x-(tickSpacing/2)-1, highY);
			ChartUtil.lineTo(context, x-tickSpacing, openY, x+(tickSpacing-1)/2, openY);
		} else {
			// defaults to HLC charts
			ChartUtil.lineTo(context, x-(tickSpacing/2)-1, lowY, x-(tickSpacing/2)-1, highY);
			ChartUtil.lineTo(context, x-tickSpacing, openY, x+(tickSpacing/2)-1, openY);			
		}
	}
}